package com.dede.mcp_tool;

import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.zip.ZipFile;

import static com.dede.mcp_tool.AndroidMCPTools.CHARSET_NAME;
import static com.dede.mcp_tool.AndroidMCPTools.SIGN;

class MCPTools {

    private MCPTools() {
    }

    /**
     * 生成渠道包
     *
     * @param sourceFile 源apk路径
     * @param targetDir  目标路径
     * @param channels   渠道名
     */
    public static void make(File sourceFile, String targetDir, String... channels) {
        if (!sourceFile.exists()) {
            System.out.println("SourceFile don't exists\t" + sourceFile.getAbsolutePath());
            return;
        }
        if (sourceFile.isDirectory()) {
            System.out.println("SourceFile is directory\t" + sourceFile.getAbsolutePath());
            return;
        }
        if (!sourceFile.getName().endsWith(".apk")) {
            System.out.println("SourceFile don't apk file\t" + sourceFile.getAbsolutePath());
            return;
        }
        File targetFileDir = new File(targetDir);
        if (targetFileDir.isFile()) {
            System.out.println("TargetDir is file\t" + targetDir);
            return;
        }

        System.out.println("Source File\t" + sourceFile.getAbsolutePath());
        System.out.println("Target Path\t" + targetDir);
        System.out.println();

        int count = channels.length;
        int successCount = 0;
        int failedCount = 0;

        long start = System.currentTimeMillis();
        for (String channel : channels) {
            System.out.println("===>>>\tStart Make Channel Package \"" + channel + "\"\t<<<===");
            File targetFile = new File(targetFileDir, sourceFile.getName().replace(".apk", "_" + channel + ".apk"));
            boolean nioTransferCopy = nioTransferCopy(sourceFile, targetFile);
            if (nioTransferCopy) {
                System.out.println("Copy the \"" + targetFile.getAbsolutePath() + "\" file Successfully");
                boolean result = write(targetFile, channel);
                if (result) {
                    System.out.println("Make the \"" + targetFile.getAbsolutePath() + "\" channel package Successfully");
                    String readChannel = AndroidMCPTools.read(targetFile);
                    System.out.println("Channel is the \"" + readChannel + "\" of the package \"" + targetFile.getAbsolutePath() + "\"");
                    successCount++;
                } else {
                    System.out.println("Make the \"" + targetFile.getAbsolutePath() + "\" channel package Failed!!!!!!");
                    failedCount++;
                }
            } else {
                System.out.println("Copy the \"" + targetFile.getAbsolutePath() + "\" file Failed!!!!!!");
                failedCount++;
            }
            System.out.println("===>>>\tComplete Make Channel Package \"" + channel + "\"\t<<<===");
            System.out.println();
        }
        long end = System.currentTimeMillis();

        System.out.println("Total:" + count + ",\tSucceed:" + successCount + ",\tFailed:" + failedCount);
        System.out.println("Life timer:" + (end - start) + "ms");
    }

    /**
     * 写入数据
     *
     * @param path    文件路径
     * @param channel 渠道名
     * @return 写入结果
     * @throws IOException
     */
    private static boolean write(File path, String channel) {
        if (channel == null || channel.equals("")) {
            System.out.println("channel == null");
            return false;
        }

        //检测是否已经写入过comment
        //zipFile.getComment()添加于jdk1.7
        ZipFile zipFile = null;
        try {
            zipFile = new ZipFile(path);
            boolean isIncludeComment = zipFile.getComment() != null;
            if (isIncludeComment) {
                System.out.println(path.getAbsolutePath() + "Zip comment is exists, Repeated write is not recommended.");
                return false;
            }
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            close(zipFile);
        }

        //拼接写入内容
        ByteArrayOutputStream bao = null;
        RandomAccessFile raf = null;
        try {
            byte[] channelName = channel.getBytes(CHARSET_NAME);
            bao = new ByteArrayOutputStream();
            bao.write(channelName);//写入渠道名
            bao.write(short2Stream((short) channelName.length));//写入渠道内容长度
            bao.write(SIGN.getBytes(CHARSET_NAME));//写入标识位
            byte[] data = bao.toByteArray();
            if (data.length > Short.MAX_VALUE) {
                System.out.println(path.getAbsolutePath() + "Zip comment length > 32767.");
                return false;
            }

            // Zip文件末尾数据结构
            /**
             * Zip文件结构
             * 参考 http://blog.csdn.net/a200710716/article/details/51644421
             *
             * End of central directory record(EOCD) 目录结束标识
             * 目录结束标识存在于整个归档包的结尾，用于标记压缩的目录数据的结束。每个压缩文件必须有且只有一个EOCD记录。
             *
             * Offset	Bytes	Description	                译
             * ...
             * 20	    2	    .ZIP file comment length(n)	注释长度
             * 22	    n	    .ZIP Comment	            注释内容
             */
            raf = new RandomAccessFile(path, "rw");
            raf.seek(path.length() - 2); // comment长度是short类型
            raf.write(short2Stream((short) data.length)); // 重新写入comment长度，注意Android apk文件使用的是ByteOrder.LITTLE_ENDIAN（小端序）；
            raf.write(data);

            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            close(bao);
            close(raf);
        }
    }

    /**
     * nio高速拷贝文件
     *
     * @param source 源文件路径
     * @param target 目标路径
     * @return
     * @throws IOException
     */
    private static boolean nioTransferCopy(File source, File target) {
        FileChannel in = null;
        FileChannel out = null;
        FileInputStream inStream = null;
        FileOutputStream outStream = null;
        try {
            File parent = target.getParentFile();
            if (parent != null && !parent.exists()) {
                parent.mkdirs();
            }
            inStream = new FileInputStream(source);
            outStream = new FileOutputStream(target);
            in = inStream.getChannel();
            out = outStream.getChannel();
            return in.transferTo(0, in.size(), out) == in.size();
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        } finally {
            close(inStream);
            close(in);
            close(outStream);
            close(out);
        }
    }

    /**
     * 关闭数据流
     *
     * @param closeable
     */
    private static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * short转换成字节数组（小端序）
     *
     * @param data
     * @return
     */
    private static byte[] short2Stream(short data) {
        ByteBuffer buffer = ByteBuffer.allocate(2);
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        buffer.putShort(data);
        buffer.flip();
        return buffer.array();
    }

}
